/*
 * This file is part of the Sx Framework Library.
 * 
 * Copyright (C) 2014 University of Colorado Denver
 * <min.choi@ucdenver.edu> <shane.transue@ucdenver.edu>
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy 
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
 * copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in 
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
 * DEALINGS IN THE SOFTWARE.
 */
#include <sxBezier_Archetype.h>
#include <sxDistances.h>

Sx::Graphics::Archetypes::Objects::Bezier_Archetype::Bezier_Archetype(const std::shared_ptr<RendererImplementation>& renderer) :
	GeometricObject_Archetype(std::make_shared<BezierCurve>(), renderer, std::make_shared<GeometricRepresentation>()) {
}

Sx::Graphics::Archetypes::Objects::Bezier_Archetype::Bezier_Archetype(const std::shared_ptr<RendererImplementation>& renderer, const Eigen::Vector3f& endPoint0, const Eigen::Vector3f& endPoint1, unsigned int n) :
	GeometricObject_Archetype(std::make_shared<BezierCurve>(endPoint0, endPoint1), renderer, std::make_shared<GeometricRepresentation>()) {
	this->curvePoints = n;
}

Sx::Graphics::Archetypes::Objects::Bezier_Archetype::Bezier_Archetype(const std::shared_ptr<RendererImplementation>& renderer, const Eigen::Vector3f& endPoint0, const Eigen::Vector3f& endPoint1, const Eigen::Vector3f& controlPoint0, const Eigen::Vector3f& controlPoint1, unsigned int n) :
	GeometricObject_Archetype(std::make_shared<BezierCurve>(endPoint0, endPoint1, controlPoint0, controlPoint1), renderer, std::make_shared<GeometricRepresentation>()) {
	this->curvePoints = n;
}

Sx::Graphics::Archetypes::Objects::Bezier_Archetype::~Bezier_Archetype() {}

/* Quick implementation of interpolation for the points of the Bezier curve. */
void CalculateBezierCurvePoint(const Eigen::Vector3f& ep0, const Eigen::Vector3f& ep1, const Eigen::Vector3f& cp0, const Eigen::Vector3f& cp1, float t, Eigen::Vector3f& point) {
	float u = 1 - t, tt = t * t, uu = u * u, uuu = uu * u, ttt = tt* t;
	Eigen::Vector3f p = uuu * ep0;
	p += 3 * uu * t * ep1;
	p += 3 * u * tt * cp0;
	p += ttt * cp1;
	point = p;
}

bool Sx::Graphics::Archetypes::Objects::Bezier_Archetype::construct() {
	if ( this->object == nullptr ) return false;
	if ( this->physicalRepresentation == nullptr ) return false;
	if ( this->curvePoints <= 2 ) this->curvePoints = 3;

	//--------------------------------------------------------------------------
	// Construct the Bezier curve from the representation based on the number
	// of curve points used to approximate the curve.
	//--------------------------------------------------------------------------
	std::shared_ptr<BezierCurve> curve = this->object;

	//--------------------------------------------------------------------------
	// This obviously isn't the fastest implementation but it is the most
	// robust way of doing it. Need to restore the state of the physical
	// representation back to the default so it can be filled again.
	//--------------------------------------------------------------------------
	this->physicalRepresentation->vertices.clear();
	this->physicalRepresentation->vertices.resize(this->curvePoints + 1);
	Eigen::Vector3f point;
	float t = 0.0f;

	const Eigen::Vector3f ep0 = curve->getEndPoint(false).getPosition();
	const Eigen::Vector3f ep1 = curve->getEndPoint(true).getPosition();
	const Eigen::Vector3f cp0 = curve->getControlPoint(false).getPosition();
	const Eigen::Vector3f cp1 = curve->getControlPoint(true).getPosition();
	for ( unsigned int i = 0; i <= this->curvePoints; i++ ) {
		t = static_cast<float>(i) / static_cast<float>(this->curvePoints);
		CalculateBezierCurvePoint(ep0, cp0, cp1, ep1, t, point);
		this->physicalRepresentation->vertices[i] = point;
	}

	return true;
}

void Sx::Graphics::Archetypes::Objects::Bezier_Archetype::update(float dt) const { return; }

bool Sx::Graphics::Archetypes::Objects::Bezier_Archetype::pick(const Eigen::Vector3f& pickSegmentStart, const Eigen::Vector3f& pickSegmentEnd, Eigen::Vector3f& intersectionPoint) {
	if ( this->object == nullptr ) return false;
	if ( this->physicalRepresentation == nullptr ) return false;
	std::shared_ptr<BezierCurve> curve = this->object;

	//--------------------------------------------------------------------------
	// Calculate the distance between the the picking ray, the two end points
	// and the control points. This distance will be used to determine what
	// points are selected.
	//--------------------------------------------------------------------------
	float ep0_pickDistance = Math::Distances::PointToSegmentDistance(curve->getEndPoint(false).getPosition(), pickSegmentStart, pickSegmentEnd);
	float ep1_pickDistance = Math::Distances::PointToSegmentDistance(curve->getEndPoint(true).getPosition(), pickSegmentStart, pickSegmentEnd);
	float cp0_pickDistance = Math::Distances::PointToSegmentDistance(curve->getControlPoint(false).getPosition(), pickSegmentStart, pickSegmentEnd);
	float cp1_pickDistance = Math::Distances::PointToSegmentDistance(curve->getControlPoint(true).getPosition(), pickSegmentStart, pickSegmentEnd);

	//--------------------------------------------------------------------------
	// Determine if the endpoints or control points are selected. If the point
	// was previously selected by a pick ray (before this function call) and
	// selected again, it will be deselected (for toggling the selection of
	// the point by clicking on it).
	//--------------------------------------------------------------------------
	if ( ep0_pickDistance < curve->getPointSelectionDistance() ) {
		if ( curve->isEndPointSelected(false) == true ) 
			curve->setEndPointSelected(false, false);
		else curve->setEndPointSelected(false, true);
	}
	if ( ep1_pickDistance < curve->getPointSelectionDistance() ) {
		if ( curve->isEndPointSelected(true) == true )
			curve->setEndPointSelected(true, false);
		else curve->setEndPointSelected(true, true);
	}
	if ( cp0_pickDistance < curve->getPointSelectionDistance() ) {
		if ( curve->isControlPointSelected(false) == true )
			curve->setControlPointSelected(false, false);
		else curve->setControlPointSelected(false, true);
	}
	if ( cp1_pickDistance < curve->getPointSelectionDistance() ) {
		if ( curve->isControlPointSelected(true) == true )
			curve->setControlPointSelected(true, false);
		else curve->setControlPointSelected(true, true);
	}

	return true;
}

bool Sx::Graphics::Archetypes::Objects::Bezier_Archetype::intersect(const Eigen::Vector3f& segment_p0, const Eigen::Vector3f& segment_p1, Eigen::Vector3f& intersectionPoint) {
	if ( this->object == nullptr ) return false;
	if ( this->physicalRepresentation == nullptr ) return false;
	std::shared_ptr<BezierCurve> curve = this->object;

	float ep0_pickDistance = Math::Distances::PointToSegmentDistance(curve->getEndPoint(false).getPosition(), segment_p0, segment_p1);
	float ep1_pickDistance = Math::Distances::PointToSegmentDistance(curve->getEndPoint(true).getPosition(), segment_p0, segment_p1);
	float cp0_pickDistance = Math::Distances::PointToSegmentDistance(curve->getControlPoint(false).getPosition(), segment_p0, segment_p1);
	float cp1_pickDistance = Math::Distances::PointToSegmentDistance(curve->getControlPoint(true).getPosition(), segment_p0, segment_p1);

	if ( ep0_pickDistance < curve->getPointSelectionDistance() ) {
		intersectionPoint = curve->getEndPoint(false).getPosition();
		return true;
	}

	if ( ep1_pickDistance < curve->getPointSelectionDistance() ) {
		intersectionPoint = curve->getEndPoint(true).getPosition();
		return true;
	}

	if ( cp0_pickDistance < curve->getPointSelectionDistance() ) {
		intersectionPoint = curve->getControlPoint(false).getPosition();
		return true;
	}

	if ( cp1_pickDistance < curve->getPointSelectionDistance() ) {
		intersectionPoint = curve->getControlPoint(true).getPosition();	
		return true;
	}

	return false;
}

bool Sx::Graphics::Archetypes::Objects::Bezier_Archetype::moveSelected(const Eigen::Vector3f& displacement) {
	if ( this->object == nullptr ) return false;
	if ( this->physicalRepresentation == nullptr ) return false;
	std::shared_ptr<BezierCurve> curve = this->object;

	if ( curve->isEndPointSelected(false) == true )
		curve->endPoint(false).position() += displacement;
	if ( curve->isEndPointSelected(true) == true )
		curve->endPoint(true).position() += displacement;
	if ( curve->isControlPointSelected(false) == true )
		curve->controlPoint(false).position() += displacement;
	if ( curve->isControlPointSelected(true) == true )
		curve->controlPoint(true).position() += displacement;

	//--------------------------------------------------------------------------
	// Expensive reconstruct to update the vertices of the curve approximation.
	//--------------------------------------------------------------------------
	return this->construct();
}

bool Sx::Graphics::Archetypes::Objects::Bezier_Archetype::moveEndPoint(bool index, const Eigen::Vector3f& displacement) {
	if ( this->object == nullptr ) return false;
	if ( this->physicalRepresentation == nullptr ) return false;
	std::shared_ptr<BezierCurve> curve = this->object;
	curve->endPoint(index).position() += displacement;

	return this->construct();
}

bool Sx::Graphics::Archetypes::Objects::Bezier_Archetype::moveControlPoint(bool index, const Eigen::Vector3f& displacement) {
	if ( this->object == nullptr ) return false;
	if ( this->physicalRepresentation == nullptr ) return false;
	std::shared_ptr<BezierCurve> curve = this->object;
	curve->controlPoint(index).position() += displacement;

	return this->construct();
}

void Sx::Graphics::Archetypes::Objects::Bezier_Archetype::setCurvePointCount(unsigned int n, bool forceReconstruct) {
	this->curvePoints = n;
	if ( forceReconstruct ) this->construct();
}

bool Sx::Graphics::Archetypes::Objects::Bezier_Archetype::isEndPointSelected(bool index) const {
	if ( this->object == nullptr ) return false;
	return this->object->isEndPointSelected(index);
}

bool Sx::Graphics::Archetypes::Objects::Bezier_Archetype::isControlPointSelected(bool index) const {
	if ( this->object == nullptr ) return false;
	return this->object->isControlPointSelected(index);
}

bool Sx::Graphics::Archetypes::Objects::Bezier_Archetype::isSelected() const {
	if ( this->object == nullptr ) return false;
	if ( this->physicalRepresentation == nullptr ) return false;

	std::shared_ptr<BezierCurve> curve = this->object;

	if ( curve->isEndPointSelected(false) ||
		 curve->isEndPointSelected(true) ||
		 curve->isControlPointSelected(false) ||
		 curve->isControlPointSelected(true) )
		return true;
	else return false;
	return false;
}

bool Sx::Graphics::Archetypes::Objects::Bezier_Archetype::hasPointsSelected() const {
	if ( this->object == nullptr ) return false;
	if ( this->physicalRepresentation == nullptr ) return false;

	std::shared_ptr<BezierCurve> curve = this->object;

	if ( curve->isEndPointSelected(false) ||
		 curve->isEndPointSelected(true) ||
		 curve->isControlPointSelected(false) ||
		 curve->isControlPointSelected(true) )
		return true;
	else return false;
	return false;
}

bool Sx::Graphics::Archetypes::Objects::Bezier_Archetype::isOneToOneFunction(Math::Axis dependent) const {
	if ( this->object == nullptr ) return false;
	if ( this->physicalRepresentation == nullptr ) return false;
	std::shared_ptr<BezierCurve> curve = this->object;

	float intervalXMin = FLT_MAX, intervalYMin = FLT_MAX, intervalZMin = FLT_MAX;
	float intervalXMax = -FLT_MAX, intervalYMax = -FLT_MAX, intervalZMax = -FLT_MAX;

	//--------------------------------------------------------------------------
	// Determine the interval of the curve by looking at the vertices in the 
	// curve representation.
	//--------------------------------------------------------------------------
	for ( unsigned int i = 0; i < physicalRepresentation->vertices.size(); i++ ) {
		Eigen::Vector3f& curVertex = physicalRepresentation->vertices[i];
		if ( curVertex.x() < intervalXMin ) intervalXMin = curVertex.x();
		if ( curVertex.y() < intervalYMin ) intervalYMin = curVertex.y();
		if ( curVertex.z() < intervalZMin ) intervalYMin = curVertex.z();

		if ( curVertex.x() > intervalXMax ) intervalXMax = curVertex.x();
		if ( curVertex.y() > intervalYMax ) intervalYMax = curVertex.y();
		if ( curVertex.z() > intervalZMax ) intervalZMax = curVertex.z();
	}

	switch ( dependent ) {
	case Math::X: case Math::NX: 
		if ( Math::InInterval(intervalXMin, intervalXMax, curve->endPoint(false).position().x(), false) ||
			 Math::InInterval(intervalXMin, intervalXMax, curve->endPoint(true).position().x(), false) ) {
			curve->setOneToOneFunction(false);
			return false;
		}
	break;
	case Math::Y: case Math::NY: 
		if ( Math::InInterval(intervalYMin, intervalYMax, curve->endPoint(false).position().y(), false) ||
			 Math::InInterval(intervalYMin, intervalYMax, curve->endPoint(true).position().y(), false) ) {
			curve->setOneToOneFunction(false);
			return false;
		}
	break;
	case Math::Z: case Math::NZ:
		if ( Math::InInterval(intervalZMin, intervalZMax, curve->endPoint(false).position().z(), false) ||
			 Math::InInterval(intervalZMin, intervalZMax, curve->endPoint(true).position().z(), false) ) {
			curve->setOneToOneFunction(false); 
			return false;
		}
	break;
	};

	curve->setOneToOneFunction(true);
	return true;
}

bool Sx::Graphics::Archetypes::Objects::Bezier_Archetype::hasLoop(Math::Axis axis) const {
	if ( this->object == nullptr ) return false;
	if ( this->physicalRepresentation == nullptr ) return false;
	std::shared_ptr<BezierCurve> curve = this->object;

	if ( this->physicalRepresentation->vertices.size() <= 2 ) return false;

	bool ep0_leftof_ep1 = true;
	switch ( axis ) {
	case Math::X: case Math::NX: if ( curve->endPoint(true).position().x() < curve->endPoint(false).position().x() ) ep0_leftof_ep1 = false; break;
	case Math::Y: case Math::NY: if ( curve->endPoint(true).position().y() < curve->endPoint(false).position().y() ) ep0_leftof_ep1 = false; break;
	case Math::Z: case Math::NZ: if ( curve->endPoint(true).position().z() < curve->endPoint(false).position().z() ) ep0_leftof_ep1 = false; break;
	};

	//--------------------------------------------------------------------------
	// Check to see if the next value in an increment or decrement from the 
	// current. If there is a loop then the values will not continuously 
	// increase or decrease they will fluctuate.
	//--------------------------------------------------------------------------
	float cur = 0.0f, next = 0.0f;
	for ( unsigned int i = 0; i < this->physicalRepresentation->vertices.size() - 1; i++ ) {
		switch ( axis ) {
		case Math::X: case Math::NX:
			cur = this->physicalRepresentation->vertices[i].x();
			next = this->physicalRepresentation->vertices[i+1].x();
		break;
		case Math::Y: case Math::NY:
			cur = this->physicalRepresentation->vertices[i].y();
			next = this->physicalRepresentation->vertices[i+1].y();
		break;
		case Math::Z: case Math::NZ:
			cur = this->physicalRepresentation->vertices[i].z();
			next = this->physicalRepresentation->vertices[i+1].z();
		break;
		};

		if ( ep0_leftof_ep1 ) {
			if ( next < cur ) {
				curve->setHasLoop(true);
				return true;
			}
		}
		else {
			if ( next > cur ) {
				curve->setHasLoop(true);
				return true;
			}
		}
	}

	curve->setHasLoop(false);
	return false;
}

unsigned int Sx::Graphics::Archetypes::Objects::Bezier_Archetype::getCurvePointCount() const {
	return this->curvePoints;
}

bool Sx::Graphics::Archetypes::Objects::Bezier_Archetype::getCurveApproximation(Util::ArrayList<Eigen::Vector3f>& approximationPoints) {
	approximationPoints.clear();
	unsigned int pointCount = this->physicalRepresentation->vertices.size();
	approximationPoints.resize(pointCount);

	for ( unsigned int i = 0; i < pointCount; i++ ) approximationPoints[i] = this->physicalRepresentation->vertices[i];
	return true;
}
